using System;
using Beaker.Injection;
using Moq;

namespace Beaker.Specs
{
	/// <summary>
	/// A builder for building Mocks that implement the <see cref="IFactory{TService}"/> interface.
	/// </summary>
	/// <typeparam name="TService">The type of the service the factory should return.</typeparam>
	public sealed class FactoryMockBuilder<TService>
		: MockBuilder<IFactory<TService>>
		where TService : class
	{
		private Maybe<Func<TService>> _createInstance;

		/// <summary>
		/// Let the mock return the given instance every time, when <see cref="IFactory{T}.Create"/> is called
		/// </summary>
		/// <param name="instance">The instance to  be returned by the Mock <see cref="IFactory{T}"/>.</param>
		/// <returns>The orignal builder so more setups can be done.</returns>
		public FactoryMockBuilder<TService> ReturningInstance(TService instance)
		{
			return WithCreateInstance(() => instance);
		}

		/// <summary>
		/// Let the mock call the given function to create the instance to be returned when <see cref="IFactory{T}.Create"/> is called.
		/// </summary>
		/// <param name="createInstance">The fuction that will create the instance to be returned by the call to <see cref="IFactory{T}.Create"/>.</param>
		/// <returns>The orignal builder so more setups can be done.</returns>
		public FactoryMockBuilder<TService> WithCreateInstance(Func<TService> createInstance)
		{
			_createInstance = createInstance;
			return this;
		}

		/// <summary>
		/// Setup the mock to return the instance.
		/// </summary>
		/// <param name="mock">The mock to add extra setups to.</param>
		protected override void SetupMock(Mock<IFactory<TService>> mock)
		{
			// get the configured instance creation function
			Func<TService> createInstance = _createInstance.GetValueOrDefault(() => CreateInstance);

			// setup the mock to call the function when create is called.
			mock.Setup(f => f.Create()).Returns(createInstance);
		}

		private TService CreateInstance()
		{
			// who knows if this will success, I don't care. The unit-test provider coder should provider correct instance.
			return Activator.CreateInstance<TService>();
		}
	}
}